﻿using System;
using System.Linq;
using System.Text;
using DevExpress.Xpo;
using DevExpress.ExpressApp;
using System.ComponentModel;
using DevExpress.ExpressApp.DC;
using DevExpress.Data.Filtering;
using DevExpress.Persistent.Base;
using System.Collections.Generic;
using DevExpress.ExpressApp.Model;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Persistent.Validation;
using DevExpress.Xpo.Metadata;
using System.Diagnostics;

namespace aztall.Module.BusinessObjects
{
	[DefaultClassOptions, ImageName("BO_Opportunity"), NavigationItem("Promotions and Sales")]
	[RuleCriteria("FromDate <= ToDate", DefaultContexts.Save, "FromDate <= ToDate", CustomMessageTemplate = "From Date cannot be later than To Date."), XafDisplayName("Sales Data")]
	public class DW : DomainObject
	{
		protected Customer _Customer;
		protected Sku _Sku;
		protected DateTime _FromDate;
		protected DateTime _ToDate;
		protected Metric _Metric;
		protected double _Value;
		protected Promo _Promo;

		public DW(Session session)
						: base(session)
		{
		}
		public override void AfterConstruction()
		{
			base.AfterConstruction();
		}
		protected override void OnSaving()
		{
			if (!this.IsDeleted)
			{
				XPClassInfo dwClass = Session.GetClassInfo<DW>();
				CriteriaOperator criteria = GroupOperator.Combine(GroupOperatorType.And,
																													new BinaryOperator("Oid", this.Oid, BinaryOperatorType.NotEqual),
																													new BinaryOperator("Domain.Oid", Domain.Oid),
																													new BinaryOperator("Metric.Oid", Metric.Oid),
																													new BinaryOperator("FromDate", ToDate, BinaryOperatorType.LessOrEqual),
																													new BinaryOperator("ToDate", FromDate, BinaryOperatorType.GreaterOrEqual)
																																										);
				if (Customer != null)
					criteria = GroupOperator.Combine(GroupOperatorType.And,
																					criteria,
																					new BinaryOperator("Customer.Oid", Customer.Oid));
				if (Sku != null)
					criteria = GroupOperator.Combine(GroupOperatorType.And,
																					criteria,
																					new BinaryOperator("Sku.Oid", Sku.Oid));

				SortingCollection sortProp = new SortingCollection(null);
				//CollectionCriteriaPatcher patcher = new CollectionCriteriaPatcher(false, Session.TypesManager);

				// find all overlapped records
				var vs = base.Session.GetObjects(dwClass, criteria, sortProp, 0, false, false).Cast<DW>().ToList();

				//delete fully wrap records
				var v1 = vs.Where(t => t.FromDate >= this.FromDate && t.ToDate <= this.ToDate).ToList();
				base.Session.Delete(v1);

				//trim FromDate/ToDate of existing records
				var v2 = vs.Where(t => (t.FromDate >= this.FromDate && t.FromDate <= this.ToDate && t.ToDate > this.ToDate) ||
																										 (t.ToDate >= this.FromDate && t.ToDate <= this.ToDate && t.FromDate < this.FromDate)
																								);
				foreach (DW t in v2)
				{
					if (t.ToDate > this.ToDate)
						t.FromDate = this.ToDate.AddDays(1);
					else
						t.ToDate = this.FromDate.AddDays(-1);
				}
				// split records
				var v3 = vs.Where(t => t.FromDate < this.FromDate && t.ToDate > this.ToDate);
				DW R;
				foreach (DW L in v3)
				{
					R = new DW(this.Session)
					{
						Domain = this.Session.FindObject<Domain>(new BinaryOperator("Oid", L.Domain.Oid)),
						Customer = L.Customer,
						Sku = L.Sku,
						FromDate = this.ToDate.AddDays(1),
						ToDate = L.ToDate,
						Metric = L.Metric,
						Value = L.Value
					};
					L.ToDate = this.FromDate.AddDays(-1);
				}
			}

			base.OnSaving();
		}
		[DataSourceCriteria("Domain.Oid = '@This.Domain.Oid' and Status.Category = '" + Status.ActiveInactive + "' and Status.StatusCode = 'Active'")]
		public Customer Customer
		{
			get { return _Customer; }
			set { SetPropertyValue(nameof(Customer), ref _Customer, value); }
		}
		[DataSourceCriteria("Domain.Oid = '@This.Domain.Oid' and Status.Category = '" + Status.Sku + "' and Status.StatusCode = 'Active'")]
		public Sku Sku
		{
			get { return _Sku; }
			set { SetPropertyValue(nameof(Sku), ref _Sku, value); }
		}
		[DataSourceCriteria("MetricType in ('" + MetricType.PromoVolume + "')")]
		public Metric Metric
		{
			get { return _Metric; }
			set { SetPropertyValue(nameof(Metric), ref _Metric, value); }
		}
		[RuleRequiredField(DefaultContexts.Save, SkipNullOrEmptyValues = false)]
		public DateTime FromDate
		{
			get { return _FromDate; }
			set { SetPropertyValue(nameof(FromDate), ref _FromDate, value); }
		}
		[RuleRequiredField(DefaultContexts.Save, SkipNullOrEmptyValues = false)]
		public DateTime ToDate
		{
			get { return _ToDate; }
			set { SetPropertyValue(nameof(ToDate), ref _ToDate, value); }
		}
		[ToolTip("Daily Value")]
		public double Value
		{
			get { return _Value; }
			set { SetPropertyValue(nameof(Value), ref _Value, value); }
		}
		[ToolTip("Promo")]
		public Promo Promo
		{
			get { return _Promo; }
			set { SetPropertyValue(nameof(Promo), ref _Promo, value); }
		}
		public static void Update(Promo promo)
		{
			Session Session = promo.Session;
			Session.Delete(new XPCollection<DW>(Session, CriteriaOperator.Parse("Promo = ?", promo.Oid.ToString())));
			foreach (PromoVol vol in promo.PromoVols)
			{
				CreateDW(vol, VolumeMetricCode.Uplift, () => vol.Uplift);
				CreateDW(vol, VolumeMetricCode.PPD, () => vol.PPD);
				CreateDW(vol, VolumeMetricCode.Cann, () => vol.CANN);
			}
			foreach (PromoCost cost in promo.PromoCosts)
			{
				CreateDW(cost);
			}
		}
		private static DW CreateDW(PromoVol pv, string metric, Func<double> calc)
		{
			Session Session = pv.Session;
			double value = calc();
			if (value == 0)
				return null;
			var newItem = new DW(Session);
			newItem.Metric = Metric.GetObjectByMetricCode(Session, metric);
			newItem.Promo = pv.Promo;
			newItem.Customer = pv.Customer;
			newItem.Sku = pv.Sku;
			newItem.FromDate = pv.FromDate;
			newItem.ToDate = pv.ToDate;
			newItem.Value = value;
			return newItem;
		}
		private static DW CreateDW(PromoCost cost)
		{
			Session Session = cost.Session;
			if (!cost.PromoLineCost.HasValue || cost.PromoLineCost.Value == 0)
				return null;

			var newItem = new DW(Session);
			newItem.Metric = Metric.GetObjectByMetricCode(Session, cost.Metric.MetricCode);
			newItem.Promo = cost.Promo;
			newItem.Customer = cost.Customer;
			newItem.Sku = cost.Sku;
			newItem.FromDate = cost.Promo.StartDate;
			newItem.ToDate = cost.Promo.EndDate;
			newItem.Value = cost.PromoLineCost.Value / cost.Promo.PromoDays();
			return newItem;
		}
		public static IList<CustomerSkuDW> GetObjects(Session session, DateTime fromDate, DateTime toDate, IEnumerable<string> metricCodes, int customerOid, int skuOid)
		{
			return GetObjects(session, fromDate, toDate, metricCodes, new int[] { customerOid }, new int[] { skuOid });
		}
		public static IList<CustomerSkuDW> GetObjects(Session session, DateTime fromDate, DateTime toDate, IEnumerable<string> metricCodes, IEnumerable<int> customerOids, IEnumerable<int> skuOids)
		{
			Debug.Assert(toDate >= fromDate, "DW.GetObjects: toDate cannot be less than fromDate");

			GroupOperator criteria = new GroupOperator(GroupOperatorType.And,
							new BinaryOperator("Domain.Oid", Env.CurrentDomainOid),
							new BinaryOperator("FromDate", toDate, BinaryOperatorType.LessOrEqual),
							new BinaryOperator("ToDate", fromDate, BinaryOperatorType.GreaterOrEqual)
							);

			//var Metrics = new XPCollection<Metric>(session, new InOperator("MetricCode", metricCodes)).ToList<Metric>();
			var Metrics = MetricHelper.Metrics.Where(c => metricCodes.Contains(c.MetricCode));
			if (metricCodes != null && metricCodes.Count() > 0)
				criteria = new GroupOperator(GroupOperatorType.And, criteria, new InOperator("Metric.MetricCode", metricCodes.Distinct()));

			if (customerOids != null && customerOids.Count() > 0)
				criteria = new GroupOperator(GroupOperatorType.And, criteria, new InOperator("Customer.Oid", customerOids.Distinct()));

			if (skuOids != null && skuOids.Count() > 0)
				criteria = new GroupOperator(GroupOperatorType.And, criteria, new InOperator("Sku.Oid", skuOids.Distinct()));

			List<DW> DWs = new XPCollection<DW>(session, criteria).ToList();

			int Days = (int)(toDate - fromDate).TotalDays + 1;

			var result = (from custOid in customerOids
										from skuOid in skuOids
										from metric in Metrics
										from d in Enumerable.Range(0, Days)
										let date = fromDate.AddDays(d)
										let w = DWs.Where(c => (date >= c.FromDate) && (date <= c.ToDate) && (c.Customer.Oid == custOid) && (c.Sku.Oid == skuOid) && (c.Metric.Oid == metric.Oid)).FirstOrDefault()
										select new CustomerSkuDW()
										{
											Date = date,
											CustomerOid = custOid,
											SkuOid = skuOid,
											MetricOid = metric.Oid,
											MetricCode = metric.MetricCode,
											Value = (w == null) ? 0 : w.Value
										}
																	).ToList<CustomerSkuDW>();
			return result;
		}

	}
	public struct CustomerSkuDW
	{
		public DateTime Date { get; set; }
		public int CustomerOid { get; set; }
		public int SkuOid { get; set; }
		public int MetricOid { get; set; }
		public string MetricCode { get; set; }
		public double Value { get; set; }
	}
}
